﻿using System;
using System.Linq;

namespace OmniKits
{
    using NullGuard;

    public abstract class StacklessEventArgsBase<TDerived, TResult> : EventArgs
        where TDerived : StacklessEventArgsBase<TDerived, TResult>
    {
#pragma warning disable 1591

        public delegate TResult StacklessEventHandler(object sender, TDerived args);

        private object _Sender;
        private StacklessEventHandler _NextHandler;

        private TResult CallNextHandlerInternal(object sender)
        {
            var handler = _NextHandler;
            var last = (StacklessEventHandler)handler.GetInvocationList().Last();
            _NextHandler = handler - last;

            var result = last(sender, (TDerived)this);

            if (_NextHandler != null)
                return CallNextHandlerInternal(sender);

            return result;
        }

        public TResult InvokeEventHandler(object sender, [AllowNull]StacklessEventHandler handler)
        {
            if (_NextHandler != null)
                throw new InvalidOperationException();

            if (handler == null)
                throw new NotImplementedException();

            _Sender = sender;
            _NextHandler = handler;

            try
            {
                return CallNextHandlerInternal(sender);
            }
            finally
            {
                _Sender = null;
            }
        }

        public static TResult Invoke(object sender, StacklessEventHandler handler, TDerived args)
        {
            return args.InvokeEventHandler(sender, handler);
        }

        public bool Cancel()
        {
            if (_NextHandler == null)
                return true;

            _NextHandler = null;
            return false;
        }

        public TResult Invoke(object sender)
        {
            if (_NextHandler == null)
                throw new InvalidOperationException();

            return CallNextHandlerInternal(sender);
        }

        public TResult Invoke() => Invoke(_Sender);
    }
}
